package com.logicbus.backend.server.http;

import java.io.IOException;
import java.io.InputStream;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alogic.matcher.CommonMatcher;
import com.alogic.matcher.MatcherFactory;
import com.anysoft.util.*;
import com.anysoft.util.resource.ResourceFactory;
import com.anysoft.webloader.HttpClientTool;
import com.google.re2j.Matcher;
import com.google.re2j.Pattern;
import com.logicbus.backend.IpAndServiceAccessController;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.anysoft.webloader.ServletConfigProperties;
import com.anysoft.webloader.ServletHandler;
import com.logicbus.backend.AccessController;
import com.logicbus.backend.DefaultNormalizer;
import com.logicbus.backend.Normalizer;
import com.logicbus.backend.server.MessageRouter;
import com.logicbus.models.catalog.Path;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * 基于anyWebLoader的ServletHandler
 * 
 * @author duanyy
 * 
 * @version 1.0.5 [20140412 duanyy] <br>
 * - 修改消息传递模型。<br>
 * 
 * @version 1.0.7 [20140418 duanyy] <br>
 * - 增加全局序列号功能,从Http头的GlobalSerial变量获取前端传入的全局序列号
 * 
 * @version 1.2.1 [20140614 duanyy] <br>
 * - 支持跨域服务调用
 * 
 * @version 1.2.6 [20140807 duanyy] <br>
 * - ServantPool和ServantFactory插件化
 * 
 * @version 1.2.7.2 [20140910 duanyy] <br>
 * - Normalizer降级为Servlet级别对象
 * 
 * @version 1.3.0.1 [20141031 duanyy] <br>
 * - 解决问题：框架截获了post方式的body数据，导致post过来的form数据无法获取
 * 
 * @version 1.4.0 [20141117 duanyy] <br>
 * - 将MessageDoc和Context进行合并整合 <br>
 * 
 * @version 1.6.1.2 [20141118 duanyy] <br>
 * - 支持HttpContext的数据截取,通过Servlet的变量intercept.mode来控制 <br>
 * 
 * @version 1.6.3.28 [20150708 duanyy] <br>
 * - 允许设置为缓存模式 <br>
 * 
 * @version 1.6.4.8 [20151013 duanyy] <br>
 * - CORS成了可选配置 <br>
 * 
 * @version 1.6.5.6 [20160523 duanyy] <br>
 * - 在MessageRouter中提前写出报文 <br>
 * 
 * @version 1.6.7.9 [20170201 duanyy] <br>
 * - 采用SLF4j日志框架输出日志 <br>
 * 
 * @version 1.6.8.6 [20170410 duanyy] <br>
 * - 增加Options方法的实现 <br>
 * 
 * @version 1.6.9.8 [20170821 duanyy] <br>
 * - 优化代码 <br>
 * 
 * @version 1.6.11.12 [20170123 duanyy] <br>
 * - http响应的缓存属性改成由服务来个性化控制 <br>
 * 
 * @version 1.6.12.8 [20181121 duanyy] <br>
 * - 关闭缺省的cors支持 <br>
 * 
 * @version 1.6.12.11 [20181206 duanyy] <br>
 * - 可配置是否支持options方法 <br>
 *
 * @version 1.6.13.30 [20210106 duanyy] <br>
 * - CORS支持改为定向域名支持 <br>
 *
 * @version 1.6.13.31 [20210108 duanyy] <br>
 * - 支持对Origin中域名的解析 <br>
 *
 * @version 1.6.14.6 [20210415 duanyy] <br>
 * - 可通过配置文件进行配置 <br>
 */
public class MessageRouterServletHandler implements ServletHandler,XMLConfigurable, Configurable {
	/**
	 * 访问控制器
	 */
	protected AccessController ac = null;
	
	/**
	 * 路径标准化
	 */
	protected Normalizer normalizer = null;
	
	/**
	 * 是否开启拦截模式
	 */
	protected boolean interceptMode = false;
	
	/**
	 * a logger of log4j
	 */
	protected static Logger LOG = LoggerFactory.getLogger(MessageRouterServletHandler.class);

	protected static final String DEFAULT = "java:///com/logicbus/backend/servant.entry.xml#" + MessageRouterServletHandler.class.getName();
	
	/**
	 * 是否已经获取服务器信息
	 */
	protected static boolean getServerInfo = false;
	
	/**
	 * 编码
	 */
	protected static String encoding = "utf-8";

	protected String methodAllow = "GET,PUT,POST";
	
	protected boolean corsSupport = false;
	
	protected boolean optionSupport = true;

	protected CommonMatcher corsMatcher = null;

	protected HttpClientTool clientTool = null;

	/**
	 * 用来提取Origin中域名的正则表达式
	 */
	protected static Pattern pattern = Pattern.compile("(\\w+):\\/\\/([^/:]+)(?::(\\d*))?([^#\\?]*)");

	@Override
	public void init(ServletConfig servletConfig) throws ServletException {
		ServletConfigProperties props = new ServletConfigProperties(servletConfig);

		String master = PropertiesConstants.getString(props, "servant.entry.master", DEFAULT);
		String secondary = PropertiesConstants.getString(props, "servant.entry.secondary", DEFAULT);

		ResourceFactory rf = Settings.getResourceFactory();

		InputStream in = null;
		try {
			in = rf.load(master, secondary, null);
			Document doc = XmlTools.loadFromInputStream(in);
			if (doc != null){
				configure(doc.getDocumentElement(), props);
			}
		}catch (Exception ex){
			LOG.error("Can not init servant entry with file : " + master);
		}finally{
			IOTools.close(in);
		}
	}


	@Override
	public void configure(Properties props) {
		encoding =  PropertiesConstants.getString(props,"http.encoding", encoding);
		corsSupport = PropertiesConstants.getBoolean(props, "http.cors", corsSupport);
		optionSupport = PropertiesConstants.getBoolean(props, "http.option", optionSupport);
		methodAllow = PropertiesConstants.getString(props, "http.method.allow", methodAllow);
		interceptMode = PropertiesConstants.getBoolean(props, "intercept.mode", interceptMode);
		corsMatcher = MatcherFactory.getMatcher(PropertiesConstants.getString(props,"http.cors.matcher","(wildcard)*"),props);

		if (normalizer == null) {
			String normalizerClass = PropertiesConstants.getString(props, "normalizer", DefaultNormalizer.class.getName());
			LOG.info("Normalizer is initializing,module:" + normalizerClass);
			try {
				Normalizer.TheFactory ncf = new Normalizer.TheFactory(
						Settings.getClassLoader());
				normalizer = ncf.newInstance(normalizerClass, props);
			} catch (Throwable t) {
				normalizer = new DefaultNormalizer(props);
				LOG.error("Failed to initialize Normalizer.Using default:"
						+ DefaultNormalizer.class.getName());
			}
		}

		clientTool = Settings.getToolkit(HttpClientTool.class);

		if (ac == null) {
			ac = (AccessController) Settings.get("accessController");
		}
	}

	@Override
	public void configure(Element e, Properties p) {
		XmlElementProperties props = new XmlElementProperties(e,p);
		Element elem = XmlTools.getFirstElementByPath(e,"normalizer");
		if (elem != null){
			try {
				Normalizer.TheFactory ncf = new Normalizer.TheFactory(Settings.getClassLoader());
				normalizer = ncf.newInstance(elem,props,"module",DefaultNormalizer.class.getName());
			} catch (Throwable t) {
				LOG.error("Failed to initialize Normalizer:" + XmlTools.node2String(elem));
				LOG.error(ExceptionUtils.getStackTrace(t));
			}
		}
		elem = XmlTools.getFirstElementByPath(e,"ac");
		if (elem != null){
			AccessController.TheFactory f = new AccessController.TheFactory();
			try {
				ac = f.newInstance(elem,props,"module", IpAndServiceAccessController.class.getName());
			} catch (Throwable t) {
				LOG.error("Failed to create access controller:" + XmlTools.node2String(elem));
				LOG.error(ExceptionUtils.getStackTrace(t));
			}
		}
		configure(props);
	}

	@Override
	public void doService(HttpServletRequest request,
			HttpServletResponse response, String method)
			throws ServletException, IOException {
		if (!getServerInfo){
			Settings settings = Settings.get();
			settings.SetValue("server.host", request.getLocalAddr());
			settings.SetValue("server.port", String.valueOf(request.getLocalPort()));
			LOG.info("Get server info:" + settings.GetValue("server.host", "") + ":" + settings.GetValue("server.port",""));
			getServerInfo = true;
		}
	
		// 1.2.1 duanyy
		// to support CORS
		if (corsSupport){
			String origin = request.getHeader("Origin");
			if (StringUtils.isNotEmpty(origin) && corsMatcher != null){
				Matcher matcher = pattern.matcher(origin);
				String domain = origin;
				if (matcher.find()){
					domain = matcher.group(2);
				}

				if (corsMatcher.isMatch(domain)) {
					clientTool.setResponseHeader(response,"Access-Control-Allow-Origin", origin);
					clientTool.setResponseHeader(response,"Access-Control-Allow-Credentials", "true");
				}
			}
		}
		
		if (method.equals("options")){
			if (optionSupport){
				clientTool.setResponseHeader(response,"Allow", methodAllow);
			}else{
				clientTool.sendError(response,HttpServletResponse.SC_NOT_FOUND,"core.e1000:Method options is not supported now.");
			}
		}else{
			HttpContext ctx = new HttpContext(request,response,encoding,interceptMode);
			MessageRouter.action(normalizer,request,ctx,ac);
		}
	}

	@Override
	public void destroy() {

	}

}
